package exilim.exf1.cspacker;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;

import exilim.exf1.cspacker.gui.CancelableProgressControl;
import exilim.exf1.cspacker.gui.GhostValuedTextfield;
import exilim.exf1.cspacker.gui.JGroup;
import exilim.exf1.cspacker.gui.ProgressControl;
import exilim.exf1.cspacker.gui.ProgressControl.ProgressAdapter;
import exilim.exf1.cspacker.util.FileUtil;
import exilim.exf1.cspacker.util.IconManager;
import exilim.exf1.cspacker.util.SwingUtil;
import exilim.exf1.cspacker.worker.PackageSequencesWorker;
import exilim.exf1.cspacker.worker.ReadDirectoryWorker;
import exilim.exf1.cspacker.worker.ScanFolderWorker;

/**
 * 
 * @author Janos
 * @version 15.08.2010 | 14:23:31
 * 
 */
public class CSPackerGui extends JPanel implements ActionListener {

	private static final String ACTION_TITLE_BrowseFolder = "Browse";
	private static final String ACTION_TITLE_SequenceScan = "Scan";
	private static final String ACTION_TITLE_PackageSequences = "Package";
	private static final String ACTION_TITLE_Exit = "Exit";

	private static final String GROUP_NAME_BrowseFolder = "Image files";
	private static final String GROUP_NAME_SequenceScan = "CS Sequences";
	private static final String GROUP_NAME_PackageSequences = "Package";

	private static final String TOOLTIP_TEXT_BrowseFolder = "Select the directory, in which your EX-F1 pictures reside.";
	private static final String TOOLTIP_TEXT_SequenceScan = "Searches the jpg files for CS sequences.";
	private static final String TOOLTIP_TEXT_PackageSequences = "Moves CS sequences into seperate sub-folders.";
	private static final String TOOLTIP_TEXT_Exit = "Exit the program.";

	private static final String ACTION_COMMAND_BrowseFolder = "browseFolder_action";
	private static final String ACTION_COMMAND_SequenceScan = "sequenceScan_action";
	private static final String ACTION_COMMAND_PackageSequences = "packageSequences_action";
	private static final String ACTION_COMMAND_Exit = "exit_action";

	private static final String TF_TEXT_SELECT_SOURCE_DIRECTORY = "<select source directory>";

	private static final String ICON_PROGRAM_ICON = "cspacker_16x16";
	private static final String ICONS_PATH = "icon";
	private static final String ICON_BROWSE = "browse";
	private static final String ICON_SCAN = "search";
	private static final String ICON_PACKAGE = "package";
	private static final String ICON_SUCCESS = "success";
	private static final String ICON_EXIT = "exit";

	private static final String WINDOW_TITLE = "EX-F1 CS Sequence Packager";
	private static final String DIALOG_TITLE_BrowseFolder = "Select Casio EX-F1 pictures directory";

	private static final long serialVersionUID = 4527529031101868191L;

	private static final Insets INSETS = new Insets(3, 2, 3, 2);

	private final JPanel group_scanSequencesGroup;
	private final JPanel group_selectFolderGroup;
	private final JPanel group_packageSequencesGroup;

	private final JButton btn_browseFolder;
	private final JButton btn_sequenceScan;
	private final JButton btn_packageSequences;

	private final JTextField tf_selectedFolder;
	private final JLabel tf_numberOfSourceFiles;
	private final JLabel tf_sequenceScan;

	private final ProgressControl<File[]> prbar_browseFolder;
	private final CancelableProgressControl<Map<Integer, Map<Integer, File>>> prbar_sequenceScan;
	private final ProgressControl<Boolean> prbar_packageSequences;

	/**
	 * Result for the gathering of all the JPEG files in the "Browse" task. <br>
	 * It contains all JPEG files in the selected directory.
	 */
	private File[] result_sourceJpgFiles;

	/**
	 * Result for the scan of the selected JPEG files in the "Scan" task. <br>
	 * It contains a map of all found image sequences.
	 */
	private Map<Integer, Map<Integer, File>> result_sequencesScan;
	private File selectedDirectory;

	public CSPackerGui() {

		super();

		final GridBagLayout gbl = new GridBagLayout();

		this.btn_browseFolder = new JButton(ACTION_TITLE_BrowseFolder);
		this.btn_browseFolder.setActionCommand(ACTION_COMMAND_BrowseFolder);
		this.btn_browseFolder.addActionListener(this);
		this.btn_browseFolder.setIcon(IconManager.getInstance().getIcon(ICON_BROWSE));
		this.btn_browseFolder.setToolTipText(TOOLTIP_TEXT_BrowseFolder);

		this.tf_selectedFolder = new GhostValuedTextfield(TF_TEXT_SELECT_SOURCE_DIRECTORY);

		this.tf_numberOfSourceFiles = new JLabel();
		this.tf_numberOfSourceFiles.setVisible(false);

		// this.prbar_browseFolder = new JProgressBar(0, 100);
		this.prbar_browseFolder = new ProgressControl<File[]>(true);
		this.prbar_browseFolder.setVisible(false);

		this.group_selectFolderGroup = new JGroup(GROUP_NAME_BrowseFolder, gbl);
		SwingUtil.addComponent(this.group_selectFolderGroup, gbl, this.tf_selectedFolder, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this.group_selectFolderGroup, gbl, this.btn_browseFolder, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.EAST,
				GridBagConstraints.NONE, INSETS);
		SwingUtil.addComponent(this.group_selectFolderGroup, gbl, this.prbar_browseFolder, 0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.EAST,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this.group_selectFolderGroup, gbl, this.tf_numberOfSourceFiles, 0, 2, 2, 1, 1.0, 1.0,
				GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, INSETS);

		this.btn_sequenceScan = new JButton(ACTION_TITLE_SequenceScan);
		this.btn_sequenceScan.setActionCommand(ACTION_COMMAND_SequenceScan);
		this.btn_sequenceScan.addActionListener(this);
		this.btn_sequenceScan.setIcon(IconManager.getInstance().getIcon(ICON_SCAN));
		this.btn_sequenceScan.setEnabled(false);
		this.btn_sequenceScan.setToolTipText(TOOLTIP_TEXT_SequenceScan);

		this.tf_sequenceScan = new JLabel();
		this.tf_sequenceScan.setVisible(false);

		this.prbar_sequenceScan = new CancelableProgressControl<Map<Integer, Map<Integer, File>>>(false);
		this.prbar_sequenceScan.setVisible(false);

		this.group_scanSequencesGroup = new JGroup(GROUP_NAME_SequenceScan, gbl);
		SwingUtil.addComponent(this.group_scanSequencesGroup, gbl, this.btn_sequenceScan, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this.group_scanSequencesGroup, gbl, this.prbar_sequenceScan, 0, 1, 1, 1, 1.0, 1.0,
				GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this.group_scanSequencesGroup, gbl, this.tf_sequenceScan, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, INSETS);

		this.btn_packageSequences = new JButton(ACTION_TITLE_PackageSequences);
		this.btn_packageSequences.setActionCommand(ACTION_COMMAND_PackageSequences);
		this.btn_packageSequences.addActionListener(this);
		this.btn_packageSequences.setIcon(IconManager.getInstance().getIcon(ICON_PACKAGE));
		this.btn_packageSequences.setEnabled(false);
		this.btn_packageSequences.setToolTipText(TOOLTIP_TEXT_PackageSequences);

		this.prbar_packageSequences = new ProgressControl<Boolean>(false);
		this.prbar_packageSequences.setVisible(false);

		this.group_packageSequencesGroup = new JGroup(GROUP_NAME_PackageSequences, gbl);
		SwingUtil.addComponent(this.group_packageSequencesGroup, gbl, this.btn_packageSequences, 0, 0, 1, 1, 1, 1,
				GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this.group_packageSequencesGroup, gbl, this.prbar_packageSequences, 0, 1, 1, 1, 1, 1,
				GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, INSETS);

		final JButton exitButton = new JButton(ACTION_TITLE_Exit);
		exitButton.setActionCommand(ACTION_COMMAND_Exit);
		exitButton.addActionListener(this);
		exitButton.setIcon(IconManager.getInstance().getIcon(ICON_EXIT));
		exitButton.setToolTipText(TOOLTIP_TEXT_Exit);

		this.setLayout(gbl);
		SwingUtil.addComponent(this, gbl, this.group_selectFolderGroup, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this, gbl, this.group_scanSequencesGroup, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this, gbl, this.group_packageSequencesGroup, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.SOUTH,
				GridBagConstraints.HORIZONTAL, INSETS);
		SwingUtil.addComponent(this, gbl, exitButton, 0, 3, 1, 1, 1, 1, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, INSETS);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	@Override
	public void actionPerformed(final ActionEvent e) {
		if (e == null) {
			return;
		}
		if (ACTION_COMMAND_BrowseFolder.equals(e.getActionCommand())) {
			action_browseFolder();
		} else if (ACTION_COMMAND_SequenceScan.equals(e.getActionCommand())) {
			action_sequenceScan();
		} else if (ACTION_COMMAND_PackageSequences.equals(e.getActionCommand())) {
			action_packageSequences();
		} else if (ACTION_COMMAND_Exit.equals(e.getActionCommand())) {
			action_exit();
		}
	}

	private void action_exit() {
		System.exit(0);
	}

	private void action_packageSequences() {

		disableBrowseFolder();
		SwingUtil.setEnabledForChildren(this.group_scanSequencesGroup, false);

		this.prbar_packageSequences.setVisible(true);

		final PackageSequencesWorker task = new PackageSequencesWorker(this.selectedDirectory, this.result_sequencesScan);

		this.prbar_packageSequences.execute(task, new ProgressAdapter<Boolean>() {
			@Override
			public void finished(final Boolean result) {
				action_packageSequences_complete(result);
			}
		});

		refreshUI();
	}

	private void action_packageSequences_complete(final Boolean result) {
		if (result) {
			final ImageIcon successIcon = IconManager.getInstance().getIcon(ICON_SUCCESS);
			JOptionPane.showMessageDialog(this, "Packaging was successful.", "Success", JOptionPane.INFORMATION_MESSAGE, successIcon);
		} else {
			JOptionPane.showMessageDialog(this, "There were errors during packaging.\nPlease check the target directory.", "Warning",
					JOptionPane.WARNING_MESSAGE);
		}

		disablePackageSequences();
		this.tf_numberOfSourceFiles.setVisible(false);
		disableSequeneScan();
		SwingUtil.setEnabledForChildren(this.group_selectFolderGroup, true);

		refreshUI();

	}

	private void action_sequenceScan() {

		disableBrowseFolder();

		this.tf_sequenceScan.setVisible(false);
		this.prbar_sequenceScan.setVisible(true);

		this.btn_sequenceScan.setEnabled(false);

		disablePackageSequences();

		final ScanFolderWorker task = new ScanFolderWorker(this.result_sourceJpgFiles);

		this.prbar_sequenceScan.execute(task, new ProgressAdapter<Map<Integer, Map<Integer, File>>>() {
			@Override
			public void cancelled() {
				CSPackerGui.this.action_sequenceScan_cancelled();
			}

			@Override
			public void finished(final Map<Integer, Map<Integer, File>> result) {
				CSPackerGui.this.action_sequenceScan_complete(result);
			}

			@Override
			public void progress(final int progress) {
				CSPackerGui.this.btn_sequenceScan.setText(String.format("Scanning: %d%%", progress));
			}
		});

		refreshUI();
	}

	private void action_sequenceScan_cancelled() {

		// Enable Group "BrowseFolder"
		SwingUtil.setEnabledForChildren(this.group_selectFolderGroup, true);

		this.result_sequencesScan = null;

		this.btn_sequenceScan.setEnabled(true);
		this.btn_sequenceScan.setText(ACTION_TITLE_SequenceScan);

		this.tf_sequenceScan.setVisible(false);
		this.prbar_sequenceScan.setVisible(false);

		refreshUI();
	}

	private void action_sequenceScan_complete(final Map<Integer, Map<Integer, File>> resultMap) {

		// Enable Group "BrowseFolder"
		SwingUtil.setEnabledForChildren(this.group_selectFolderGroup, true);

		this.result_sequencesScan = resultMap;

		this.prbar_sequenceScan.setVisible(false);

		this.btn_sequenceScan.setEnabled(true);
		this.btn_sequenceScan.setText(ACTION_TITLE_SequenceScan);

		this.tf_sequenceScan.setVisible(true);
		this.tf_sequenceScan.setText(String.format("%d sequences found.", resultMap == null ? 0 : resultMap.size()));

		resetPackageSequences();

		refreshUI();

	}

	private void action_browseFolder() {

		final File selectionInput = new File(this.tf_selectedFolder.getText());
		this.selectedDirectory = FileUtil.promtForDirectory(selectionInput, this, DIALOG_TITLE_BrowseFolder);

		if (this.selectedDirectory == null) {
			return;
		}

		this.tf_selectedFolder.setText(this.selectedDirectory.getAbsolutePath());

		this.tf_numberOfSourceFiles.setVisible(false);
		this.prbar_browseFolder.setVisible(true);
		this.btn_browseFolder.setEnabled(false);
		this.tf_selectedFolder.setEnabled(false);

		disableSequeneScan();
		disablePackageSequences();

		final ReadDirectoryWorker task = new ReadDirectoryWorker(this.selectedDirectory);

		this.prbar_browseFolder.execute(task, new ProgressAdapter<File[]>() {
			@Override
			public void finished(final File[] result) {
				CSPackerGui.this.action_browseFolder_complete(result);
			}
		});

		refreshUI();
	}

	private void action_browseFolder_complete(final File[] sourceFiles) {

		this.result_sourceJpgFiles = sourceFiles;

		this.prbar_browseFolder.setVisible(false);
		this.btn_browseFolder.setEnabled(true);
		this.tf_selectedFolder.setEnabled(true);

		resetSequenceScan();

		final int numFiles = sourceFiles == null ? 0 : sourceFiles.length;

		this.tf_numberOfSourceFiles.setText(String.format("%d image files in directory.", numFiles));
		this.tf_numberOfSourceFiles.setVisible(true);

		this.btn_packageSequences.setEnabled(false);

		refreshUI();

	}

	private void disableBrowseFolder() {
		// Disable Group "BrowseFolder"
		SwingUtil.setEnabledForChildren(this.group_selectFolderGroup, false);
	}

	private void disableSequeneScan() {
		this.prbar_sequenceScan.setVisible(false);
		this.tf_sequenceScan.setVisible(false);
		this.btn_sequenceScan.setEnabled(false);
	}

	private void resetSequenceScan() {
		this.result_sequencesScan = null;
		this.prbar_sequenceScan.setVisible(false);
		this.tf_sequenceScan.setEnabled(true);
		this.tf_sequenceScan.setVisible(false);
		this.btn_sequenceScan.setEnabled(this.result_sourceJpgFiles != null && this.result_sourceJpgFiles.length > 0);
		disablePackageSequences();
	}

	private void disablePackageSequences() {
		this.prbar_packageSequences.setVisible(false);
		this.btn_packageSequences.setEnabled(false);
	}

	private void resetPackageSequences() {
		this.prbar_packageSequences.setVisible(false);
		this.btn_packageSequences.setEnabled(this.result_sequencesScan != null && this.result_sequencesScan.size() > 0);
	}

	private void refreshUI() {
		this.revalidate();
		this.repaint();

		final JFrame parent = (JFrame) this.getParent().getParent().getParent();
		parent.pack();
	}

	/**
	 * Create the GUI and show it. As with all GUI code, this must run on the event-dispatching
	 * thread.
	 */
	private static void createAndShowGUI() {
		final JFrame frame = new JFrame(WINDOW_TITLE);
		final ImageIcon icon = IconManager.getInstance().getIcon(ICON_PROGRAM_ICON);
		if (icon != null) {
			frame.setIconImage(icon.getImage());
		}
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		final JComponent content = new CSPackerGui();
		content.setOpaque(true);
		frame.setContentPane(content);
		frame.setResizable(false);
		frame.pack();

		centerFrame(frame);

		frame.setVisible(true);

	}

	private static void centerFrame(final JFrame frame) {

		final Rectangle bounds = frame.getBounds();
		final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();

		bounds.x = (screen.width / 2) - (bounds.width / 2);
		bounds.y = (screen.height / 2) - (bounds.height / 2);

		frame.setBounds(bounds);

	}

	public static void main(final String[] args) {

		IconManager.getInstance().setIconFolder(ICONS_PATH);

		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (final Exception e) {
			e.printStackTrace();
		}

		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				createAndShowGUI();
			}
		});

	}

}
